﻿/*
 * BinaryHelper
 * 
 * Author: snowdreamist
 * Date: 2011/11/19
 * 
 */

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BitTorrentSharp.Helpers
{
    public static class BinaryHelper
    {
        #region bytes
        
        public static int CompareBytes(byte[] byteA, byte[] byteB)
        {
            if (byteA == null && byteB == null)
                return 0;
            else if (byteB == null)
                return -1;
            else if (byteA == null)
                return 1;

            int index = 0;

            while (true)
            {
                if (byteA.Length <= index && byteB.Length <= index)
                    return 0;
                else if (byteA.Length <= index)
                    return -1;
                else if (byteB.Length <= index)
                    return 1;

                if (byteA[index] > byteB[index])
                    return 1;
                else if (byteA[index] < byteB[index])
                    return -1;

                index++;
            }

        }

        public static int GetBytesHashCode(this byte[] source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            return source.Select(p => (int)p).Sum();
        }

        public static byte[] Xor(this byte[] b1, byte[] b2)
        {
            if (b1 == null)
                throw new ArgumentNullException("b1");
            if (b2 == null)
                throw new ArgumentNullException("b2");
            if (b1.Length != b2.Length)
                throw new ArgumentException("b1's length must be equal to b2's length");

            byte[] result = new byte[b1.Length];

            for (int i = 0; i < b1.Length; ++i)
                result[i] = (byte)(b1[i] ^ b2[i]);

            return result;
        }
        /// <summary>
        /// Convert to UInt64[], little ending
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static UInt64[] ConvertToUInt64s(this byte[] source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            // fill 0 in order to make source's length can divided by 8
            if (source.Length % 8 != 0)
            {
                byte[] newSource = new byte[(source.Length / 8 + 1) * 8];
                source.CopyTo(newSource, 0);
                source = newSource;
            }

            UInt64[] result = new UInt64[source.Length / 8];

            for (int i = 0; i < result.Length; ++i)
                result[i] = BitConverter.ToUInt64(source, i * 8);

            return result;
        }

        #endregion

        #region UInt64

        public static int CompareUInt64s(UInt64[] int1, UInt64[] int2)
        {
            throw new NotImplementedException();
        }

        public static int GetUInt64HashCode(this UInt64[] source)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            return (int)source.Select(p => (int)p).Sum();
        }

        public static UInt64[] Xor(this UInt64[] int1, UInt64[] int2)
        {
            UInt64[] result = new UInt64[Math.Max(int1.Length, int2.Length)];

            int minLength = Math.Min(int1.Length, int2.Length);

            for (int i = 0; i < minLength; ++i)
                result[i] = int1[i] ^ int2[i];

            if (int1.Length > int2.Length)
                for (int i = minLength; i < int1.Length; ++i)
                    result[i] = int1[i];
            else if (int2.Length > int1.Length)
                for (int i = minLength; i < int2.Length; ++i)
                    result[i] = int2[i];

            return result;
        }
        
        public static byte[] ConvertToBytes(this UInt64[] source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            IEnumerable<byte> result = null;

            for (int i = 0; i < source.Length; ++i)
                if (result == null)
                    result = BitConverter.GetBytes(source[i]);
                else
                    result = result.Concat(BitConverter.GetBytes(source[i]));

            return result.ToArray();
        }

        #endregion

        #region binary reader

        private const int InitializeArraySize = 128;

        public static string ReadStringUntil(this BinaryReader reader, byte stopByte)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");
            return ReadStringUntil(reader, stopByte, Encoding.Default);
        }

        public static string ReadStringUntil(this BinaryReader reader, byte stopByte, Encoding encoding)
        {
            if(reader == null)
                throw new ArgumentNullException("reader");
            if(encoding == null)
                throw new  ArgumentNullException("encoding");

            byte[] buffer = new byte[BinaryHelper.InitializeArraySize];
            int index = 0;

            while (true)
            {
                byte b = reader.ReadByte();
                if (b == stopByte)
                    break;
                if (index >= buffer.Length)
                    buffer = CollectionHelper.ExpandOneTime<byte>(buffer);
                buffer[index++] = b;
            }

            return encoding.GetString(buffer, 0, index);
        }

        public static byte[] ReadBytesUntil(this BinaryReader reader, byte stopByte)
        {
            byte[] buffer = new byte[BinaryHelper.InitializeArraySize];
            int index = 0;

            while (true)
            {
                byte b = reader.ReadByte();
                if (b == stopByte)
                    break;
                if (index >= buffer.Length)
                    buffer = CollectionHelper.ExpandOneTime<byte>(buffer);
                buffer[index++] = b;
            }

            return buffer.Take(index).ToArray();
        }

        #endregion

        #region number
        /// <summary>
        /// big ending, high bits in low address
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static UInt32 ConvertToUInt32WithBigEnding(this byte[] source)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (source.Length > 4)
                throw new ArgumentException("source's max length cannot be larger than 4");
            if (source.Length == 0)
                return 0;

            UInt32 value = 0;

            for (int i = 0; i < source.Length; ++i)
                value = ((value << 8) | source[i]);

            return value;
        }
        /// <summary>
        /// little ending, high bits in high address
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static UInt32 ConvertToUInt32WithLittleEnding(this byte[] source)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (source.Length > 4)
                throw new ArgumentException("source's max length cannot be larger than 4");
            if (source.Length == 0)
                return 0;

            UInt32 value = 0;

            for (int i = source.Length - 1; i >= 0; --i)
                value = ((value << 8) | source[i]);

            return value;
        }

        #endregion
    }
}
